package com.tech.distribute_lock.redis;

import com.tech.distribute_lock.DistributeLockStategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.locks.LockSupport;


/**
 * @author litianlin
 * @date 2023/12/6 14:24
 * @description: 锁：阻塞、排他、可重入
 * lua，加锁、解锁原子化
 * 解锁时判定拥有锁
 * 可重入。hash结构
 */
@Slf4j
@Component
public class RedisDistributeLockStategy implements DistributeLockStategy {
    private static final Long RELEASE_SUCCESS = 1L;
    /**
     eval "if redis.call('setnx', KEYS[1], ARGV[1])==0 then return 0 else redis.call('pexpire', KEYS[1], ARGV[2]); return 1 end" 1 a2 b2 200
     */
    private static final String LOCK_SCRIPT_EXPR = "if redis.call('setnx', KEYS[1], ARGV[1])==0 then return 0 else redis.call('pexpire', KEYS[1], ARGV[2]); return 1 end";
    private static final RedisScript LOCK_SCRIPT = RedisScript.of(LOCK_SCRIPT_EXPR);
    private static final String UNLOCK_SCRIPT_EXPR = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    private static final RedisScript UNLOCK_SCRIPT = RedisScript.of(UNLOCK_SCRIPT_EXPR);

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 尝试获取分布式锁
     * @param lockKey 锁
     * @param expireTime 超期时间，ms
     * @return 是否获取成功
     */
    @Override
    public boolean tryLock(String lockKey, int expireTime) {
        String ownerId = getOwnerId();

        Object result = redisTemplate.execute(LOCK_SCRIPT, Collections.singletonList(lockKey), Arrays.asList(ownerId, expireTime));
        boolean flag = RELEASE_SUCCESS.equals(result);
        return flag;

    }

    @Override
    public void lockWait(int millis){
        LockSupport.parkNanos(millis*1000*1000);
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁
     * @return 是否释放成功
     */
    @Override
    public boolean unLock(String lockKey) {
        String ownerId = getOwnerId();
        Object result = redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(ownerId));

        boolean flag = RELEASE_SUCCESS.equals(result);
        return flag;

    }
}
